import os, sys
import sqlalchemy as sa

APPLICATION_INSTALL_DIR = os.environ.get('SIMPLEAUTH_INSTALL')
if not APPLICATION_INSTALL_DIR:
    raise EnvironmentError("Must Set SIMPLEAUTH_INSTALL in the environment!")

sys.path.append(os.path.join(APPLICATION_INSTALL_DIR, "lib%spython" % os.sep))

from simple_auth.config import DEFAULT_CONFIG_PATH, ConfigLoader
from simple_auth import model as simple_model
from simple_auth import envbuilder 

env = envbuilder.EnvironmentBuilder()


def get_engine(uri=None):
    engine = sa.create_engine(str(uri))    
    return engine

def get_metadata(uri=None):
    engine = get_engine(str(uri))
    metadata = sa.BoundMetaData(engine)
    return metadata

def get_session(uri=None):
    engine = get_engine(str(uri))
    return sa.create_session(bind_to=engine)


def flush_with_trans(session, errors=[]):
    trans = session.create_transaction(autoflush=False)
    try:
        session.flush()
    except Exception, e:
        errors.append(e)
        trans.rollback()
        trans.close()
    else:
        trans.commit()
        trans.close()
    return errors


class ResourceBuilderException(Exception):
    """to be thrown when necessary elements of the config or db are unavailable"""


class ResourceBuilder(object):
    """resource builder config loader and base class"""
    def __init__(self, configName, configPath=None):
        if configPath is None:
            configPath = DEFAULT_CONFIG_PATH
        resource_name = "resource_" + configName
        try:
            self.config = ConfigLoader(configPath).load(resource_name)
        except:
            try:
                possibleConfig = os.path.join(os.path.dirname(__file__), "conf")
                self.config = ConfigLoader(possibleConfig).load(resource_name)
            except:
                raise

    def build(self):
        """this should build and sync the configured resource"""
        raise NotImplementedError("subclass must implement")




class DBResourceBuilder(ResourceBuilder):
    """remote database resource builder and populator"""
    
    class AnyUser(object):
        """Any remote user object"""
        pass
    
    def __init__(self, *args, **kwargs):
        super(DBResourceBuilder, self).__init__(*args, **kwargs)
        # columns that have been added already
        tracked_cols = []
        
        # build primary key columns
        primary_key_cols = []
        for pk in self.config.db.auth_table.primary_key.column:
            dataType = getattr(sa, str(pk.datatype))
            dataValue = str(pk.value)
            tracked_cols.append(str(pk.name))
            col = sa.Column(str(pk.name), dataType(dataValue), primary_key=True, key=getattr(pk, "alt", None))
            primary_key_cols.append(col)
        
        # build other mapped columns
        other_cols = []
        for col in self.config.db.auth_table.mapped_cols.column:
            dataType = getattr(sa, str(col.datatype))
            dataValue = str(col.value)
            if str(col.name) in tracked_cols:
                continue
            else:
                tracked_cols.append(str(col.name))
            sacol = sa.Column(str(col.name), dataType(dataValue), key=getattr(col, "alt", None))
            other_cols.append(sacol)
        
        all_cols = primary_key_cols + other_cols
        
        self.users_table = sa.Table(str(self.config.db.auth_table.name), get_metadata(str(self.config.db.uri)), *all_cols)
        self.AnyUser.mapper = sa.mapper(self.AnyUser, self.users_table)


    def build(self):
        if str(self.config.enabled).lower() != "true":
            raise ResourceBuilderException("Will not build unenabled resource!")
        resource_session = get_session(str(self.config.db.uri))
        simple_session = get_session(str(env.simple_dburi))
        
        # get the actual resource object
        resource_object = simple_session.query(simple_model.Resource).get_by(name=str(self.config.remote.name))
        if not resource_object:
            resource_session.close()
            simple_session.close()
            raise ResourceBuilderException("No such resource!  Create resource by name: %s in the simple_auth db first!" % str(self.config.remote.name))
        
        
        # get base roles
        base_role_names = [str(role) for role in self.config.defaults.xml_xpath("role")]
        base_roles = [simple_session.query(simple_model.Role).get_by(name=role_name) for role_name in base_role_names]
        try:
            base_roles.remove(None)
        except ValueError:
            # just don't want "none" in there for roles, if the xml somehow is misconfigured
            pass
        
        # get resource users from remote
        resource_users = resource_session.query(self.AnyUser).select()
        built_users = []
        for u in resource_users:
            user = simple_session.query(simple_model.ResourceUser).selectfirst(sa.and_(simple_model.ResourceUser.c.name==u.username, simple_model.ResourceUser.c.resource_id==resource_object.id))
            if not user:
                user = simple_model.ResourceUser(name=u.username)
                user.resource_id = resource_object.id
            user.password = u.password or ' '
            # computationally costly comprehension probably (at least a bit)
            # but keeps us from appending a username and an id that already exist
            # there's no constraint on the table that can safely keep this from happening really
            if (user.name, user.resource_id) not in [(usr.name, usr.resource_id) for usr in built_users]:
                built_users.append(user)
                simple_session.save_or_update(user)

        # now for all the users we just built, make sure they have the default roles
        for user in built_users:
            for br in base_roles:
                if br not in user.roles:
                    user.roles.append(br)
                simple_session.save_or_update(user)
                simple_session.save_or_update(br)
        
        # close the resource session
        resource_session.close()
        # flush our simple auth session changes, and close
        errors = flush_with_trans(simple_session)
        simple_session.close()
        if len(errors) > 0:
            raise ResourceBuilderException("Unable to Save! %s" % errors)



